﻿using System;
using System.Collections.Generic;
using System.Configuration;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Bymed.SpineScan.DataModel;
using Bymed.SpineScan.Repository.Interface;
using Bymed.SpineScan.Service.Interface;
using Bymed.SpineScan.Service.Shared;
using Bymed.SpineScan.Service.Validation;

namespace Bymed.SpineScan.Service
{
    public class PatientService : ServiceBase<Patient>, IPatientService
    {
        private IPatientRepository _patientRepository;

        public PatientService(IRepositoryFactory repositoryFactory)
            : base(repositoryFactory, new PatientValidator()) { }

        public override void Delete(Patient entity)
        {
            using (RepositoryFactory)
            {
                RepositoryFactory.OpenSession();
                _patientRepository = RepositoryFactory.CreatePatientRepository();

                // Delete all related Test data
                DeletePatientTestData(entity);

                // Delete current Patient data.
                _patientRepository.Delete(entity);

                RepositoryFactory.Commit();
            }
        }

        public override void Delete(IEnumerable<Patient> entities)
        {
            using (RepositoryFactory)
            {
                RepositoryFactory.OpenSession();
                _patientRepository = RepositoryFactory.CreatePatientRepository();

                foreach (var entity in entities)
                {
                    // Delete all related Test data
                    DeletePatientTestData(entity);

                    // Delete current Patient data.
                    _patientRepository.Delete(entity);
                }

                RepositoryFactory.Commit();
            }
        }

        /// <summary>
        /// Delete all related Test data ONLY of specified Patient.
        /// </summary>
        /// <param name="patient">The specified Patient whom all related test data will be deleted.</param>
        public void DeleteRelatedTestDataOnly(Patient patient)
        {
            using (RepositoryFactory)
            {
                RepositoryFactory.OpenSession();
                DeletePatientTestData(patient);

                RepositoryFactory.Commit();
            }
        }

        /// <summary>
        /// Get new patient number
        /// </summary>
        /// <returns>Patient number</returns>
        public string GetPatientNumber()
        {
            var numberHelper = new ConfiguredNumberHelper(ConfigurationConstant.PatientNumberPrefix,
                ConfigurationConstant.PatientNumberContainsYear,
                ConfigurationConstant.PatientNumberContainsMonth,
                ConfigurationConstant.PatientNumberContainsDay,
                ConfigurationConstant.PatientNumberInitializedSequence);

            var number = string.Empty;

            using (RepositoryFactory)
            {
                RepositoryFactory.OpenSession();
                var patientRepository = RepositoryFactory.CreatePatientRepository();

                // Get current max patient number based on provided full prefix.

                IQueryable<Patient> matchedPatients = patientRepository.Query(p => p.Number.Length == numberHelper.FixedLength);

                if (!string.IsNullOrEmpty(numberHelper.FullPrefix))
                {
                    // If contains prefix.

                    matchedPatients = matchedPatients.Where(p => p.Number
                        .StartsWith(numberHelper.FullPrefix));
                }

                var currentPatientWithMaxNumber = matchedPatients.OrderByDescending(p => p.Number).Take(1).FirstOrDefault();

                // Get the new generated sample number.
                number = numberHelper.GenerateNewNumber(currentPatientWithMaxNumber == null ? string.Empty : currentPatientWithMaxNumber.Number);
            }

            return number;
        }

        /// <summary>
        /// Search the patient data based on provided TestType.
        /// </summary>
        /// <param name="patient">Patient instance contains the search condition values.</param>
        /// <param name="testDateType">Selected TestDateType.</param>
        /// <returns>Matched Patient data list.</returns>
        public IList<Patient> SearchByTestDateType(Patient patient, TestDateType testDateType)
        {
            using (RepositoryFactory)
            {
                RepositoryFactory.OpenSession();
                _patientRepository = RepositoryFactory.CreatePatientRepository();

                IList<Patient> result = new List<Patient>();
                IEnumerable<Patient> rawResult = null;

                switch (testDateType)
                {
                    case TestDateType.All:
                        rawResult = GetAllPatientData(patient);
                        break;
                    case TestDateType.NewAdded:
                        rawResult = GetPatientDataOfNewAdded(patient);
                        break;
                    case TestDateType.Testing:
                        rawResult = GetPatientDataOfBeenTesting(patient);
                        break;
                    case TestDateType.Tested:
                        rawResult = GetPatientDataOfBeenTested(patient);
                        break;
                }

                if (rawResult != null)
                {
                    result = rawResult.OrderByDescending(p => p.CreateOn).ToList();
                }

                return result;
            }
        }

        /// <summary>
        /// Get all patient data and all related test results.
        /// </summary>
        /// <returns></returns>
        public IList<Patient> GetAllWithTestResults()
        {
            using (RepositoryFactory)
            {
                RepositoryFactory.OpenSession();
                _patientRepository = RepositoryFactory.CreatePatientRepository();

                return _patientRepository.GetAll("TestTrackings;TestTrackings.TestResults").ToList();
            }
        }

        /// <summary>
        /// Get max patient number by prefix in data source.
        /// </summary>
        /// <param name="prefix">The full prefix of the patient number except the sequence number.</param>
        /// <returns>The max patient number</returns>
        private string GetMaxPatientNumberByPrefixInDataSource(string prefix)
        {
            using (RepositoryFactory)
            {
                RepositoryFactory.OpenSession();
                _patientRepository = RepositoryFactory.CreatePatientRepository();

                var matchedPatient = (from patient in _patientRepository.Query(p => p.Number.StartsWith(prefix))
                                      orderby patient.Number descending
                                      select patient).Take(1).FirstOrDefault();

                if (matchedPatient != null)
                {
                    return matchedPatient.Number;
                }
                else
                {
                    return null;
                }
            }
        }

        /// <summary>
        /// Get all the patient data.
        /// </summary>
        /// <returns>Patient List</returns>
        private IEnumerable<Patient> GetAllPatientData(Patient patient)
        {
            return from pt in _patientRepository.Query(p => (patient.Name.Length == 0 || p.Name.Contains(patient.Name)) &&
                (patient.Number.Length == 0 || p.Number.ToLower().Contains(patient.Number.ToLower())))
                   select pt;
        }

        /// <summary>
        /// Get the patient data who is new added to system and has not been tested yet.
        /// </summary>
        /// <returns>Patient List</returns>
        private IEnumerable<Patient> GetPatientDataOfNewAdded(Patient patient)
        {
                return from pt in _patientRepository.Query(p => (patient.Name.Length == 0 || p.Name.Contains(patient.Name)) &&
                    (patient.Number.Length == 0 || p.Number.ToLower().Contains(patient.Number.ToLower())) &&
                    (!p.TestTrackings.Any()))
                       select pt;
        }

        /// <summary>
        /// Get the data patient who finished testing in current day.
        /// </summary>
        /// <returns>Patient List</returns>
        private IEnumerable<Patient> GetPatientDataOfBeenTesting(Patient patient)
        {
                return from pt in _patientRepository.Query(p => (patient.Name.Length == 0 || p.Name.Contains(patient.Name)) &&
                    (patient.Number.Length == 0 || p.Number.ToLower().Contains(patient.Number.ToLower())) &&
                    (p.TestTrackings.Where(t => t.TestDate.Year == DateTime.Now.Year &&
                        t.TestDate.Month == DateTime.Now.Month &&
                        t.TestDate.Day == DateTime.Now.Day).Any()))
                       select pt;
        }

        /// <summary>
        /// Get the data patient who finished testing.
        /// </summary>
        /// <returns>Patient List</returns>
        private IEnumerable<Patient> GetPatientDataOfBeenTested(Patient patient)
        {
                return from pt in _patientRepository.Query(p => (patient.Name.Length == 0 || p.Name.Contains(patient.Name)) &&
                    (patient.Number.Length == 0 || p.Number.ToLower().Contains(patient.Number.ToLower())) &&
                    (p.TestTrackings.Any()))
                       select pt;
        }

        /// <summary>
        /// Delete all related Test data ONLY of specified Patient.
        /// </summary>
        /// <param name="testTrackingRepository">Instance of class that implements ITestTrackingRepository inteface.</param>
        /// <param name="patient">The specified Patient whom all related test data will be deleted.</param>
        private void DeletePatientTestData(Patient patient)
        {
            // Get all related TestTracking data.
            var testTrackings = RepositoryFactory.CreateTestTrackingRepository().Query(t => t.PatientNumber == patient.Number,
                "TestResults;TestResultDiagnosticMessages");

            foreach (var testTracking in testTrackings)
            {
                CommonService.DeleteTestTracking(RepositoryFactory, testTracking);
            }
        }
    }
}
